package com.witmore.skyline.web.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.witmore.skyline.db.entity.DeviceDO;
import com.witmore.skyline.db.mapper.DeviceMapper;
import com.witmore.skyline.web.controller.enums.WitmoreErrorCode;
import com.witmore.skyline.web.controller.vo.param.DeviceOilListParamVO;
import com.witmore.skyline.web.controller.vo.result.ResponseResult;
import com.witmore.skyline.web.service.DeviceOilCacheService;
import com.witmore.skyline.web.service.DeviceOilService;
import com.witmore.skyline.web.service.third.yjx.client.ApiDeviceOilClient;
import com.witmore.skyline.web.service.third.yjx.dto.*;
import com.witmore.skyline.web.utils.LocalDateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Description: DeviceOilServiceImpl
 * Author: 豆子高
 * Date: 2025/3/05 10:14
 */
@Slf4j
@Service
public class DeviceOilServiceImpl implements DeviceOilService {

    @Autowired
    private ApiDeviceOilClient apiDeviceOilClient;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceOilCacheService deviceOilCacheService;

    @Override
    public ResponseResult<List<DeviceOilDTO>> oilList(DeviceOilListParamVO deviceOilListParamVO) {
        // 校验参数
        WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
        if (null != witmoreErrorCode) {
            return ResponseResult.fail(witmoreErrorCode);
        }
        try {
            List<DeviceOilDTO> deviceOilDTOS = apiDeviceOilClient.fetchDeviceOilPercent(deviceOilListParamVO.getDeviceSn(),
                    deviceOilListParamVO.getStartTimeStr(), deviceOilListParamVO.getEndTimeStr());
            return ResponseResult.success(deviceOilDTOS);
        } catch (Exception e) {
            log.error("设备油位百分比获取失败 error msg-->{},deviceOilListParamVO-->{}, e", e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO), e);
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), e.getMessage());
        }
    }


    @Override
    public ResponseResult<AddOilDTO> refuelList(DeviceOilListParamVO deviceOilListParamVO) {
        // 校验参数
        WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
        if (null != witmoreErrorCode) {
            return ResponseResult.fail(witmoreErrorCode);
        }
        try {
            AddOilDTO addOilDTO = apiDeviceOilClient.fetchDeviceRefuel(deviceOilListParamVO.getDeviceSn(),
                    deviceOilListParamVO.getStartTimeStr(), deviceOilListParamVO.getEndTimeStr());
            // 对加油详情列表按加油时间倒序排序
            addOilDTO.getAddOilPointDTOList().sort((point1, point2) ->
                    Double.compare(point2.getAddOilTime(), point1.getAddOilTime())
            );
            return ResponseResult.success(addOilDTO);
        } catch (Exception e) {
            log.error("设备加油列表获取失败 error msg-->{},deviceOilListParamVO-->{}, e", e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO), e);
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), e.getMessage());
        }
    }


    @Override
    public ResponseResult<List<DeviceOilConsumptionDTO>> oilConsumptionList(DeviceOilListParamVO deviceOilListParamVO) {
        // 校验参数
        WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
        if (null != witmoreErrorCode) {
            return ResponseResult.fail(witmoreErrorCode);
        }
        try {
            List<DeviceOilConsumptionDTO> deviceOilConsumptionDTOS = apiDeviceOilClient.fetchDeviceFuelConsumption(deviceOilListParamVO.getDeviceSn(),
                    deviceOilListParamVO.getStartTimeStr(), deviceOilListParamVO.getEndTimeStr());
            // 转换时间 时间戳转成 String的 日期
            if (!CollectionUtils.isEmpty(deviceOilConsumptionDTOS)) {
                deviceOilConsumptionDTOS.forEach(item -> {
                    if (null != item.getDate()) {
                        item.setTime(LocalDateUtils.timestampConvertString(item.getDate()));
                    }
                });
            }
            return ResponseResult.success(deviceOilConsumptionDTOS);
        } catch (Exception e) {
            log.error("设备用油量统计获取失败 error msg-->{},deviceOilListParamVO-->{}, e",
                    e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO));
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), e.getMessage());
        }
    }

    @Override
    public ResponseResult<List<DeviceAvgFuelConsumptionDTO>> avgFuelConsumptionList(DeviceOilListParamVO deviceOilListParamVO) {
        // 校验参数
        WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
        if (null != witmoreErrorCode) {
            return ResponseResult.fail(witmoreErrorCode);
        }
        try {
            List<DeviceAvgFuelConsumptionDTO> deviceAvgFuelConsumptionDTOS = apiDeviceOilClient.fetchDeviceAverageFuelConsumption(deviceOilListParamVO.getDeviceSn(),
                    deviceOilListParamVO.getStartTimeStr(), deviceOilListParamVO.getEndTimeStr());
            // 转换时间 时间戳转成 String的 日期
            if (!CollectionUtils.isEmpty(deviceAvgFuelConsumptionDTOS)) {
                deviceAvgFuelConsumptionDTOS.forEach(item -> {
                    if (null != item.getDate()) {
                        item.setTime(LocalDateUtils.timestampConvertString(item.getDate()));
                    }
                });
            }
            return ResponseResult.success(deviceAvgFuelConsumptionDTOS);
        } catch (Exception e) {
            log.error("设备平均用油量获取失败 error msg-->{},deviceOilListParamVO-->{}, e",
                    e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO));
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), e.getMessage());
        }
    }

    /**
     * 校验参数
     */
    private WitmoreErrorCode deviceOilParamCheck(DeviceOilListParamVO deviceOilListParamVO) {
        String deviceSn = deviceOilListParamVO.getDeviceSn();
        LocalDateTime startTime = deviceOilListParamVO.getStartTime();
        LocalDateTime endTime = deviceOilListParamVO.getEndTime();
        if (StringUtils.isEmpty(deviceSn) || null == startTime || null == endTime) {
            return WitmoreErrorCode.PARAM_ERROR;
        }
        // 校验时间格式
        if (startTime.isAfter(endTime)) {
            return WitmoreErrorCode.START_DATE_AFTER_END_DATE;
        }
        // 结束时间不能大于当前时间
        if (endTime.isAfter(LocalDateTime.now())) {
            return WitmoreErrorCode.END_DATE_AFTER_NOW;
        }
        if (ChronoUnit.DAYS.between(startTime, endTime) > 30) {
            return WitmoreErrorCode.DATE_INTERVAL_NOT_ALLOWED_30;
        }
        deviceOilListParamVO.setStartTimeStr(LocalDateUtils.dateTimeConvertString(startTime));
        deviceOilListParamVO.setEndTimeStr(LocalDateUtils.dateTimeConvertString(endTime));
        return null;
    }

    @Override
    public ResponseResult<List<DeviceRemainingOilVolumeDTO>> remainingOilVolumeList(DeviceOilListParamVO deviceOilListParamVO) {
        try {
            // 1. 参数校验
            WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
            if (witmoreErrorCode != null) {
                return ResponseResult.fail(witmoreErrorCode.getCode(), witmoreErrorCode.getMessage());
            }

            // 2. 获取油位百分比数据（使用V2接口，带缓存和分页优化）
            ResponseResult<List<DeviceOilDTO>> oilResult = this.oilListV2(deviceOilListParamVO);
            if (oilResult.getCode() != 200) {
                return ResponseResult.fail(oilResult.getCode(), "获取油位数据失败");
            }
            if (oilResult.getData() == null){
                return ResponseResult.success();
            }

            // 3. 查询设备油箱容量
            DeviceDO deviceDO = deviceMapper.selectOne(new LambdaQueryWrapper<DeviceDO>()
                    .eq(DeviceDO::getDeviceSn, deviceOilListParamVO.getDeviceSn())
                    .eq(DeviceDO::getIsDeleted, 0));

            if (deviceDO == null) {
                return ResponseResult.fail(WitmoreErrorCode.NO_DATA.getCode(), "设备不存在");
            }

            Float fuelTankCapacity = deviceDO.getFuelTankCapacity();
            if (fuelTankCapacity == null || fuelTankCapacity <= 0) {
                return ResponseResult.fail(WitmoreErrorCode.NO_DATA.getCode(), "设备油箱容量信息不存在，请先同步设备信息");
            }

            // 4. 计算剩余油量
            List<DeviceRemainingOilVolumeDTO> resultList = oilResult.getData().stream()
                    .map(oilDTO -> {
                        Float remainingVolume = null;
                        try {
                            if (StringUtils.isNotEmpty(oilDTO.getOilLevel())) {
                                Float oilPercent = Float.parseFloat(oilDTO.getOilLevel());
                                remainingVolume = (oilPercent * fuelTankCapacity) / 100;
                                // 保留一位小数
                                remainingVolume = Math.round(remainingVolume * 10) / 10.0f;
                            }
                        } catch (NumberFormatException e) {
                            log.warn("油位百分比数据格式异常: {}, deviceSn: {}", oilDTO.getOilLevel(), deviceOilListParamVO.getDeviceSn());
                        }

                        return DeviceRemainingOilVolumeDTO.builder()
                                .time(oilDTO.getTime())
                                .oilLevel(oilDTO.getOilLevel())
                                .remainingVolume(remainingVolume)
                                .fuelTankCapacity(fuelTankCapacity)
                                .build();
                    })
                    .collect(Collectors.toList());

            return ResponseResult.success(resultList);

        } catch (Exception e) {
            log.error("获取设备剩余油量统计失败 error msg-->{}, deviceOilListParamVO-->{}",
                    e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO), e);
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), "获取设备剩余油量统计失败");
        }
    }

    @Override
    public ResponseResult<List<DeviceOilDTO>> oilListV2(DeviceOilListParamVO deviceOilListParamVO) {
        // 1. 参数校验
        WitmoreErrorCode witmoreErrorCode = this.deviceOilParamCheck(deviceOilListParamVO);
        if (null != witmoreErrorCode) {
            return ResponseResult.fail(witmoreErrorCode);
        }

        try {
            LocalDate today = LocalDate.now();
            LocalDate startDate = deviceOilListParamVO.getStartTime().toLocalDate();
            LocalDate endDate = deviceOilListParamVO.getEndTime().toLocalDate();

            List<DeviceOilDTO> resultList = new ArrayList<>();

            // 2. 区分当天数据和历史数据
            List<LocalDate> historyDates = new ArrayList<>();
            LocalDate todayDate = null;

            for (LocalDate date = endDate; !date.isBefore(startDate); date = date.minusDays(1)) {
                if (date.isBefore(today)) {
                    historyDates.add(date);
                } else {
                    todayDate = date;
                }
            }

            // 3. 并行读取历史数据（缓存）
            if (!historyDates.isEmpty()) {
                List<CompletableFuture<Map.Entry<LocalDate, List<DeviceOilDTO>>>> futures = historyDates.stream()
                    .map(date -> CompletableFuture.supplyAsync(() -> {
                        List<DeviceOilDTO> dayData = getHistoryDayDataFromCache(
                                deviceOilListParamVO.getDeviceSn(), date);
                        return Map.entry(date, dayData);
                    }))
                    .toList();

                // 等待所有并行任务完成
                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

                // 按日期倒序收集结果（保持时间倒序）
                for (LocalDate date : historyDates) {
                    for (CompletableFuture<Map.Entry<LocalDate, List<DeviceOilDTO>>> future : futures) {
                        Map.Entry<LocalDate, List<DeviceOilDTO>> entry = future.join();
                        if (entry.getKey().equals(date)) {
                            resultList.addAll(entry.getValue());
                            break;
                        }
                    }
                }
            }

            // 4. 处理当天数据（带5分钟短缓存）
            if (todayDate != null) {
                String todayStart = todayDate.equals(startDate) ? deviceOilListParamVO.getStartTimeStr() : todayDate + " 00:00:00";
                String todayEnd = todayDate.equals(endDate) ? deviceOilListParamVO.getEndTimeStr() : todayDate + " 23:59:59";

                List<DeviceOilDTO> todayData = deviceOilCacheService.fetchTodayDataWithCache(
                        deviceOilListParamVO.getDeviceSn(),
                        todayStart,
                        todayEnd);

                if (todayData != null) {
                    // 当天数据插入到开头（保持时间倒序）
                    resultList.addAll(0, todayData);
                } else {
                    log.error("当天油位数据获取失败（接口异常），跳过, date={}", todayDate);
                }
            }

            return ResponseResult.success(resultList);

        } catch (Exception e) {
            log.error("油位统计V2获取失败 error msg-->{}, deviceOilListParamVO-->{}",
                    e.getMessage(), JSONObject.toJSONString(deviceOilListParamVO), e);
            return ResponseResult.fail(WitmoreErrorCode.SYSTEM_ERROR.getCode(), e.getMessage());
        }
    }

    /**
     * 获取历史某天数据（带缓存）
     * @param deviceSn 设备编号
     * @param date 日期
     * @return 油位数据列表
     */
    public List<DeviceOilDTO> getHistoryDayDataFromCache(String deviceSn, LocalDate date) {
        String cacheKey = String.format("ApiDeviceOilClient:fetchDeviceOilPercent:%s:%s",
                deviceSn, date.toString());

        // 1. 先查缓存
        String cacheValue = redisTemplate.opsForValue().get(cacheKey);

        if (StringUtils.isNotEmpty(cacheValue)) {
            // 缓存命中
            log.info("历史油位数据缓存命中, deviceSn={}, date={}", deviceSn, date);
            return JSONObject.parseArray(cacheValue, DeviceOilDTO.class);
        }

        // 2. 缓存未命中，分页获取整天数据
        log.info("历史油位数据缓存未命中，开始分页获取, deviceSn={}, date={}", deviceSn, date);
        String dayStart = date + " 00:00:00";
        String dayEnd = date + " 23:59:59";

        List<DeviceOilDTO> dayData = fetchByPagination(deviceSn, dayStart, dayEnd);

        if (dayData == null) {
            // 接口调用失败，不缓存，返回空列表
            log.error("历史油位数据获取失败（接口异常），不写入缓存, deviceSn={}, date={}", deviceSn, date);
            return new ArrayList<>();
        }

        // 3. 只有成功获取数据才写入缓存（30天过期）
        // 注意：即使 dayData 是空列表也会缓存，因为这是正常的业务情况（某天确实没有数据）
        redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(dayData),
                30, TimeUnit.DAYS);
        log.info("历史油位数据已缓存, deviceSn={}, date={}, dataSize={}", deviceSn, date, dayData.size());

        return dayData;
    }

    /**
     * 分页获取油位数据
     * @param deviceSn 设备编号
     * @param startTime 开始时间（格式：yyyy-MM-dd HH:mm:ss）
     * @param endTime 结束时间（格式：yyyy-MM-dd HH:mm:ss）
     * @return 所有数据合并后的列表，接口失败或数据不完整返回null
     */
    private List<DeviceOilDTO> fetchByPagination(String deviceSn, String startTime, String endTime) {
        List<DeviceOilDTO> allData = new ArrayList<>();
        int page = 1;  // 云机械页码从1开始
        int size = 2000;  // 云机械最大支持2000
        boolean hasMore = true;

        log.info("开始分页获取油位数据, deviceSn={}, startTime={}, endTime={}", deviceSn, startTime, endTime);

        while (hasMore) {
            // 调用云机械分页接口
            ApiResponse<List<DeviceOilDTO>> response = apiDeviceOilClient.fetchDeviceOilPercentWithPage(
                    deviceSn, startTime, endTime, page, size);

            if (response == null) {
                // 接口调用失败（可能是限流），记录日志并返回null（表示数据不完整，不应缓存）
                log.error("分页获取油位数据失败（接口调用异常/限流）, deviceSn={}, page={}, 已获取数据量={}, startTime={}, endTime={}",
                        deviceSn, page, allData.size(), startTime, endTime);
                return null;  // 返回null表示数据不完整
            }

            if (CollectionUtils.isEmpty(response.getResult())) {
                // 正常情况下数据为空，结束分页
                log.info("分页获取油位数据为空（正常结束）, deviceSn={}, page={}", deviceSn, page);
                hasMore = false;
            } else {
                List<DeviceOilDTO> pageData = response.getResult();
                allData.addAll(pageData);

                // 通过 page.last 判断是否最后一页
                boolean isLast = response.getPage() != null && Boolean.TRUE.equals(response.getPage().getLast());

                log.info("分页获取油位数据成功, deviceSn={}, page={}, pageSize={}, isLast={}, totalPages={}",
                        deviceSn, page, pageData.size(), isLast,
                        response.getPage() != null ? response.getPage().getTotalPages() : "unknown");

                if (isLast) {
                    // 最后一页，结束分页
                    hasMore = false;
                } else {
                    page++;
                }
            }
        }

        // 能执行到这里说明正常遍历完所有分页，返回完整数据
        log.info("分页获取油位数据完整完成, deviceSn={}, totalSize={}, totalPages={}", deviceSn, allData.size(), page);
        return allData;
    }
}
